/*
 * XJavaHelpLibrary.java
 * 
 * Copyright (c) 2006 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.help;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;

import nu.xom.Element;
import nu.xom.Elements;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

// A JavaHelp jar file can contain at most one help set (simplification)
// A help set can contain multiple table of contents.

/**
 * 
 * 
 * @author Devon Carew
 */
class XJavaHelpLibrary
	extends XHelpLibrary
{
	private static final String JAVAHELP_SYSTEM_ID = "http://java.sun.com/products/javahelp/";
	
	private Map idToURLMap = new HashMap();
	
	private String 		helpSetEntryName;
	private String		helpTitle;
	private XTableOfContents	contents;
	
	public XJavaHelpLibrary(File helpFile)
		throws IOException
	{
		super(helpFile);
	}
	
	private void findHelpSetEntryName()
		throws IOException
	{
		Enumeration zipEntries = getZipFile().entries();
		
		// Look for all *.hs entries.
		while (zipEntries.hasMoreElements())
		{
			ZipEntry entry = (ZipEntry)zipEntries.nextElement();
			
			if (!entry.getName().endsWith(".hs"))
				continue;
			
			helpSetEntryName = entry.getName();
			
			contents = parseHelpSet(helpSetEntryName);
			
			break;
		}
		
		if (helpSetEntryName == null)
			throw new IOException("No helpset files found in jar");
	}
	
	protected XTableOfContents parseTableOfContents()
		throws IOException
	{
		if (helpSetEntryName == null)
			findHelpSetEntryName();
		
		return contents;
	}

	public String parseTitle()
		throws IOException
	{
		if (helpSetEntryName == null)
			findHelpSetEntryName();
		
		return helpTitle;
	}
	
	private XTableOfContents parseHelpSet(String helpSetEntryName)
		throws IOException
	{
		Element root = parseXMLEntry(helpSetEntryName);
		
		// look for helpset.title
		// look for helpset.maps.homeID
		// look for helpset.maps.mapref.location
		// look for helpset.view where type == javax.help.TOCView, get data
		
		String helpSetTitle = root.getFirstChildElement("title").getValue().trim();
		
		this.helpTitle = helpSetTitle;
		
		Element maps = root.getFirstChildElement("maps");
		
		//String homeID = maps.getFirstChildElement("homeID").getValue();
		
		String mapLocation = maps.getFirstChildElement("mapref").getAttributeValue("location").trim();
		
		mapLocation = makeAbsoluteEntry(mapLocation, helpSetEntryName);
		
		if (mapLocation == null)
			throw new IOException("Unable to locate map file");
		
		// Parse the jhm file.
		parseMapFile(parseXMLEntry(mapLocation), mapLocation);
		
		Elements views = root.getChildElements("view");
		
		List tocs = new ArrayList();
		
		for (int i = 0; i < views.size(); i++)
		{
			Element view = views.get(i);
			
			String viewType = view.getFirstChildElement("type").getValue().trim();
			
			// Support both JavaHelp libraries and Oracle's help libraries.
			if ("javax.help.TOCView".equals(viewType) || "oracle.help.navigator.tocNavigator.TOCNavigator".equals(viewType))
			{
				String tocLabel = view.getFirstChildElement("label").getValue().trim();
				String tocLocation = view.getFirstChildElement("data").getValue().trim();
				
				if (tocLocation == null)
					throw new IOException("Unable to locate TOC file");
				
				tocLocation = makeAbsoluteEntry(tocLocation, helpSetEntryName);
				
				// Parse the TOC file.
				tocs.add(parseTOCFile(helpSetTitle, tocLabel, parseXMLEntry(tocLocation)));
			}
		}
		
		XTableOfContents toc = null;
		
		if (tocs.size() == 0)
		{
			throw new IOException("No TOCViews found in helpset file");
		}
		else if (tocs.size() == 1)
		{
			toc = (XTableOfContents)tocs.get(0);
		}
		else
		{
			toc = new XTableOfContents(helpSetTitle, "TOC");
			
			for (Iterator itor = tocs.iterator(); itor.hasNext(); )
			{
				XTableOfContents child = (XTableOfContents)itor.next();
				
				child.setTitle(child.getTOCLabel());
				
				toc.getChildren().add(child);
			}
		}
		
		return toc;
	}
	
	private void parseMapFile(Element root, String mapLocation)
		throws IOException
	{
		//<map version="1.0">
		//	<mapID target="fish" url="Pages/Fish_Desc.html" />
		//	<mapID target="amphibians" url="Pages/Amphibians_Desc.html" />
		//</map>
		
		Elements mapIDs = root.getChildElements("mapID");
		
		for (int i = 0; i < mapIDs.size(); i++)
		{
			Element mapID = mapIDs.get(i);
			
			String targetID = mapID.getAttributeValue("target").trim();
			String url = mapID.getAttributeValue("url").trim();
			
			url = makeAbsoluteEntry(url, mapLocation);
			
			idToURLMap.put(targetID, url);
		}
	}

	private XTableOfContents parseTOCFile(String helpSetTitle, String tocLabel, Element root)
		throws IOException
	{
		//<toc version="1.0">
		//  <tocitem text="Animal Categories">
		//    <tocitem text="Vertebrates" target="vertebrates" >
		//	  <tocitem text="Fish" target="fish" />
		//	  <tocitem text="Amphibians" target="amphibians" />
		//	  <tocitem text="Reptiles" target="reptiles" />
		//	  <tocitem text="Birds" target="birds" />
		//	  <tocitem text="Mammals" target="mammals" >
		//		<tocitem text="Marsupials" target="marsupials" />
		//		<tocitem text="Primates" target="primates" />
		//		<tocitem text="Rodents" target="rodents" />
		
		XTableOfContents xtoc = new XTableOfContents(helpSetTitle, tocLabel);
		
		Elements items = root.getChildElements("tocitem");
		
		if (items.size() > 0)
			parseTOCItems(xtoc.getChildren(), items);
		
		return xtoc;
	}
	
	private void parseTOCItems(List list, Elements items)
	{
		for (int i = 0; i < items.size(); i++)
		{
			Element item = items.get(i);
			
			String target = item.getAttributeValue("target");
			
			XContentsItem tocItem = null;
			
			if (target == null)
				tocItem = new XContentsItem(item.getAttributeValue("text"));
			else
				tocItem = new XContentsItem(item.getAttributeValue("text"), target, (String)idToURLMap.get(target));
			
			list.add(tocItem);
			
			Elements children = item.getChildElements("tocitem");
			
			if (children.size() > 0)
				parseTOCItems(tocItem.getChildren(), children);
		}
	}

	protected EntityResolver getEntityResolver()
	{
		return new LocalEntityResolver();
	}
	
	protected String getXMLSystemID()
	{
		return JAVAHELP_SYSTEM_ID;
	}
	
	/**
	 * A class to resolve the locally stored JavaHelp DTDs.
	 */
	static class LocalEntityResolver
		implements EntityResolver
	{
		public InputSource resolveEntity(String publicId, String systemId)
			throws SAXException, IOException
		{
			int index = systemId.lastIndexOf('/');
			
			if (index != -1)
				systemId = systemId.substring(index + 1);
			
			InputStream in = XJavaHelpLibrary.class.getResourceAsStream(systemId);
			
			if (in != null)
			{
				InputSource source = new InputSource(in);
				
				source.setSystemId(JAVAHELP_SYSTEM_ID);
				
				return source;
			}
			
			return null;
		}
	}

}
